package mongo

import (
	"context"
	"gogo/log"
	"gogo/utils"
	"runtime"
	"strings"

	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

const (
	Action_Find             = 1
	Action_FindOne          = 2
	Action_FindOneAndUpdate = 3
	Action_FindOneAndDelete = 4
	Action_Distinct         = 5
	Action_CountDocuments   = 6
	Action_InsertMany       = 7
	Action_InsertOne        = 8
	Action_ReplaceOne       = 9
	Action_DeleteOne        = 10
	Action_DeleteMany       = 11
	Action_UpdateMany       = 12
	Action_UpdateOne        = 13
)

// asyn: 是否异步
// db_name: 数据库名
// table_name: 表名
// action: 动作
// filter: 过滤条件
// arg1: 附加参数
// opts: 附加参数
func (c *Mongo_Class) UtilsMongoShell(db_name, table_name string, action int, filter interface{}, arg1 interface{}, opts ...interface{}) interface{} {

	if utils.IsDebug() {
		log.This().DebugF("Mongo: db_name:%s table_name:%s action:%d", db_name, table_name, action)
	}

	if db_name == "" || table_name == "" || action == 0 {
		log.This().ErrorF("DoShell db_name:%s table_name:%s action:%d", db_name, table_name, action)
		return nil
	}

	var ret interface{}
	var err error

	for {
		coll := c.GetMGClient().Database(db_name).Collection(table_name)
		if coll == nil {
			log.This().ErrorF("ERROR: Mongo DoShell db_name:%s table_name:%s", db_name, table_name)
			utils.This().TimeSleepSecond(1)
			runtime.Gosched()
			continue
		}

		switch action {
		case Action_Find:
			var opts2 []*options.FindOptions
			for _, opt := range opts {
				opts2 = append(opts2, opt.(*options.FindOptions))
			}
			if ret, err = coll.Find(context.TODO(), filter, opts2...); ret != nil && err == nil {
				return ret
			}

		case Action_FindOne:
			var opts2 []*options.FindOneOptions
			for _, opt := range opts {
				opts2 = append(opts2, opt.(*options.FindOneOptions))
			}
			if ret = coll.FindOne(context.TODO(), filter, opts2...); ret != nil {
				if err = ret.(*mongo.SingleResult).Err(); err == nil {
					return ret
				}
			}

		case Action_FindOneAndDelete:
			var opts2 []*options.FindOneAndDeleteOptions
			for _, opt := range opts {
				opts2 = append(opts2, opt.(*options.FindOneAndDeleteOptions))
			}
			if ret = coll.FindOneAndDelete(context.TODO(), filter, opts2...); ret != nil {
				if err = ret.(*mongo.SingleResult).Err(); err == nil {
					return ret
				}
			}

		case Action_FindOneAndUpdate:
			var opts2 []*options.FindOneAndUpdateOptions
			for _, opt := range opts {
				opts2 = append(opts2, opt.(*options.FindOneAndUpdateOptions))
			}
			if ret = coll.FindOneAndUpdate(context.TODO(), filter, arg1, opts2...); ret != nil {
				if err = ret.(*mongo.SingleResult).Err(); err == nil {
					return ret
				}
			}

		case Action_Distinct:
			var opts2 []*options.DistinctOptions
			for _, opt := range opts {
				opts2 = append(opts2, opt.(*options.DistinctOptions))
			}
			if ret, err = coll.Distinct(context.TODO(), arg1.(string), filter, opts2...); ret != nil && err == nil {
				return ret
			}

		case Action_CountDocuments:
			var opts2 []*options.CountOptions
			for _, opt := range opts {
				opts2 = append(opts2, opt.(*options.CountOptions))
			}
			if ret, err = coll.CountDocuments(context.TODO(), filter, opts2...); ret != nil && err == nil {
				return ret
			}

		case Action_InsertMany:
			var opts2 []*options.InsertManyOptions
			for _, opt := range opts {
				opts2 = append(opts2, opt.(*options.InsertManyOptions))
			}
			if ret, err = coll.InsertMany(context.TODO(), arg1.([]interface{}), opts2...); ret != nil && err == nil {
				return ret
			}

		case Action_InsertOne:
			var opts2 []*options.InsertOneOptions
			for _, opt := range opts {
				opts2 = append(opts2, opt.(*options.InsertOneOptions))
			}
			if ret, err = coll.InsertOne(context.TODO(), arg1, opts2...); ret != nil && err == nil {
				return ret
			}

		case Action_ReplaceOne:
			var opts2 []*options.ReplaceOptions
			for _, opt := range opts {
				opts2 = append(opts2, opt.(*options.ReplaceOptions))
			}
			if ret, err = coll.ReplaceOne(context.TODO(), filter, arg1, opts2...); ret != nil && err == nil {
				return ret
			}

		case Action_DeleteOne:
			var opts2 []*options.DeleteOptions
			for _, opt := range opts {
				opts2 = append(opts2, opt.(*options.DeleteOptions))
			}
			if ret, err = coll.DeleteOne(context.TODO(), filter, opts2...); ret != nil && err == nil {
				return ret
			}

		case Action_DeleteMany:
			var opts2 []*options.DeleteOptions
			for _, opt := range opts {
				opts2 = append(opts2, opt.(*options.DeleteOptions))
			}
			if ret, err = coll.DeleteMany(context.TODO(), filter, opts2...); ret != nil && err == nil {
				return ret
			}

		case Action_UpdateMany:
			var opts2 []*options.UpdateOptions
			for _, opt := range opts {
				opts2 = append(opts2, opt.(*options.UpdateOptions))
			}
			if ret, err = coll.UpdateMany(context.TODO(), filter, arg1, opts2...); ret != nil && err == nil {
				return ret
			}

		case Action_UpdateOne:
			var opts2 []*options.UpdateOptions
			for _, opt := range opts {
				opts2 = append(opts2, opt.(*options.UpdateOptions))
			}
			if ret, err = coll.UpdateOne(context.TODO(), filter, arg1, opts2...); ret != nil && err == nil {
				return ret
			}
		}

		if mongo.IsTimeout(err) {
			utils.This().TimeSleepSecond(1)
			runtime.Gosched()
			continue
		}

		if mongo.IsNetworkError(err) {
			//p_mongo_client_info.FreeMGClient()
			utils.This().TimeSleepSecond(1)
			runtime.Gosched()
			continue
		}

		if mongo.IsDuplicateKeyError(err) {
			runtime.Gosched()
			//log.This().Debug("************* mongo.IsDuplicateKeyError")
			break
		}

		if !strings.Contains(err.Error(), "no documents in result") && !strings.Contains(err.Error(), "document is nil") {
			log.This().Error(err)
			runtime.Gosched()
			break
		}

		break
	}

	return nil
}
